28 农产品交易可视化性分析
28.1 引言农产品电商的数据价值
农产品电子商务的快速发展产生了海量交易数据。通过可视化分析,我们可以:
发现业务规律:
- 识别季节性需求和价格波动模式
- 发现区域消费偏好差异
- 优化供应链和库存管理
28.2 本章学习目标
本章以农产品电商交易数据为对象,完整走一遍探索性数据分析(EDA)的图形流程。通过本章学习,你将掌握:
- EDA的工作顺序:先体检(结构、缺失、重复),再看分布,后看关系
- 数值特征的箱线图/直方图与类别特征的计数柱状图、饼图的选型依据
- 用
map+ 字典做产品名到品类的映射,并用fillna兜底保留原值 - 用
subplot在一张画布上组织多幅子图 - 从顾客、商品、销售指标、渠道与优惠四个视角形成结构化读图结论
先修内容:第 章节 19 章(Seaborn图形基础)与第 章节 20 章(常用图形绘制);缺失值检查回看第 章节 16 章。
28.3 数据准备与探索
理论背景:探索性数据分析(EDA)
本章的分析流程遵循探索性数据分析(Exploratory Data Analysis, EDA)的范式。该方法由统计学家John Tukey于1977年提出,其核心思想是在进行正式建模之前,通过统计图形和摘要统计来理解数据的特征。
EDA的四个核心原则:
- 怀疑精神: 对数据和假设保持质疑
- 可视化优先: 图形比数字更直观
- 计算支撑: 用统计量验证视觉发现
- 迭代探索: 分析是循环往复的过程
本章随后的数据体检、分布分析、时间序列探索与多变量关系考察,正是按这四条原则展开的。
28.3.1 数据读取与初步检查
任务要求:读取平台内置的 processed_data.csv,完成数据结构、缺失值、重复值体检;绘制价格、数量、顾客年龄、是否返单、销售额的箱型图;用字典把七个产品名映射为茶叶、食用菌等品类;随后分视角可视化——顾客信息(年龄直方图、年龄组别与性别计数柱状图、性别饼图)、区域分布、商品信息(产品名称与品类计数图)、销售指标(价格、数量、销售额直方图)、平台与优惠信息(渠道、优惠情况、是否返单的计数柱状图与饼图)。
平台任务(平台原始代码)
以下代码与教学平台任务要求完全一致:
# 注:processed_data.csv数据文件本地没有,但平台已经内置
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
# 导入必要的数据分析和可视化库
import pandas as pd
import numpy as np # 导入NumPy数值计算库
import matplotlib.pyplot as plt # 导入Matplotlib绘图库
import seaborn as sns # 导入Seaborn可视化库
import warnings # 导入warnings模块用于控制警告输出
warnings.filterwarnings('ignore') # 忽略运行时警告信息以保持输出整洁
plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体作为默认字体以支持中文
plt.rcParams['axes.unicode_minus'] = False # 正确显示负号
# 读取处理好的数据集
data = pd.read_csv('processed_data.csv')
# 查看数据集的基本情况
print(data.head()) # 显示数据集前几行
print("查看数据集的基本信息:") # 输出查看数据集的基本信息
print(data.info()) # 显示数据集的基本信息,包括列名、数据类型和非空值数量
print("查看数据集有无缺失值:") # 输出查看数据集有无缺失值
print(data.isnull().sum()) # 统计每列的缺失值数量
print("查看数据集有无重复值:") # 输出查看数据集有无重复值
print(data.duplicated().sum()) # 统计重复行的数量
# 创建特征映射字典,将英文列名映射为中文名称,用于图表标题
feature_map = {
'price': '价格', # 映射"price"→"价格"
'quantity': '数量', # 映射"quantity"→"数量"
'customer_age': '顾客年龄', # 映射"customer_age"→"顾客年龄"
'return_flag': '是否返单', # 映射"return_flag"→"是否返单"
'sales_amount': '销售额' # 映射"sales_amount"→"销售额"
} # 数据结构定义结束
# 创建箱型图展示各特征的分布情况
plt.figure(figsize=(20,10)) # 设置图形大小
for i, feature in enumerate(feature_map.keys()): # 循环遍历
plt.subplot(2,3, i+1) # 创建2行3列的子图布局
plt.title(f'{feature_map[feature]}的箱型图') # 设置子图标题
plt.boxplot(data[feature]) # 绘制箱线图
plt.xlabel(f'{feature_map[feature]}') # 设置x轴标签
plt.ylabel('频率') # 设置y轴标签
plt.savefig("特征箱型图.png") # 保存图形
# 创建一个字典来映射产品名称到正确的分类
category_mapping = {
'安溪铁观音': '茶叶', # 映射"安溪铁观音"→"茶叶"
'武夷岩茶': '茶叶', # 映射"武夷岩茶"→"茶叶"
'福州茉莉花': '茶叶', # 映射"福州茉莉花"→"茶叶"
'古田银耳': '食用菌', # 映射"古田银耳"→"食用菌"
'建宁莲子': '中药材', # 映射"建宁莲子"→"中药材"
'琯溪蜜柚': '水果', # 映射"琯溪蜜柚"→"水果"
'宁德大黄鱼': '水产品' # 映射"宁德大黄鱼"→"水产品"
} # 数据结构定义结束
# 应用映射来更新category列,将产品名称转换为产品类别
data['category'] = data['product_name'].map(category_mapping).fillna(data['category'])
# 2.可视化展示 - 计算并转置描述性统计数据(结果未打印,仅计算)
data.describe().T
# 2.1 顾客信息可视化
plt.figure(figsize=(20,10)) # 设置图形大小
# 顾客年龄分布直方图
plt.subplot(2,2,1)
sns.distplot(data['customer_age'], bins=30, kde=True, color='blue') # 绘制带核密度估计的直方图
plt.title('顾客年龄分布', fontsize=16) # 设置标题
plt.xlabel('顾客年龄', fontsize=12) # 设置x轴标签
plt.ylabel('频率', fontsize=12) # 设置y轴标签
# 顾客年龄组别分布柱状图
plt.subplot(2,2,2)
order = ['18-25', '26-35', '36-45', '46-55', '56+'] # 自定义年龄组顺序
sns.countplot(data=data, x='age_group', color='orange', order=order) # 按指定顺序绘制柱状图
plt.title('顾客年龄组别分布', fontsize=16) # 设置标题
plt.xlabel('顾客年龄组别', fontsize=12) # 设置x轴标签
plt.ylabel('频率', fontsize=12) # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
plt.gca().annotate(format(p.get_height(), '.0f'), # 获取当前坐标轴并在柱状图顶部标注数值
(p.get_x() + p.get_width() / 2., p.get_height()), # 设置标注位置为柱子顶部中心
# 设置标注文本的水平对齐、垂直偏移等显示参数
ha='center', va='center', xytext=(0, 5), textcoords='offset points')
# 顾客性别分布柱状图
plt.subplot(2,2,3)
sns.countplot(data=data, x='customer_gender', color='green') # 绘制性别分布柱状图
plt.title('顾客性别分布', fontsize=16) # 设置标题
plt.xlabel('顾客性别', fontsize=12) # 设置x轴标签
plt.ylabel('频率', fontsize=12) # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
plt.gca().annotate(format(p.get_height(), '.0f'), # 获取当前坐标轴并在柱状图顶部标注数值
(p.get_x() + p.get_width() / 2., p.get_height()), # 设置标注位置为柱子顶部中心
# 设置标注文本的水平对齐、垂直偏移等显示参数
ha='center', va='center', xytext=(0, 5), textcoords='offset points')
# 顾客性别比例饼图
plt.subplot(2,2,4)
plt.pie(data['customer_gender'].value_counts(), # 绘制饼图
labels=data['customer_gender'].value_counts().index, # 统计各个值的出现频次
autopct='%1.1f%%', colors=['green', 'orange']) # 绘制饼图并显示百分比
plt.title('顾客性别比例', fontsize=16) # 设置标题
plt.axis('equal') # 确保饼图是圆形
plt.savefig("顾客信息.png") # 保存图形
#1、从顾客年龄组别的分布可以了解到,26-55岁间的顾客最多。
#2、从顾客性别的分布可以了解到,男性顾客比女性顾客多,男性占比60.4%,女性占比39.6%。
# 区域分布可视化
plt.figure(figsize=(20,5)) # 设置图形大小
sns.countplot(x='region', data=data, palette='Set2') # 绘制计数柱状图
plt.title('区域分布', fontsize=20) # 设置标题
plt.xlabel('区域', fontsize=16) # 设置x轴标签
plt.ylabel('数量', fontsize=16) # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
plt.gca().annotate(format(p.get_height(), '.0f'), # 获取当前坐标轴并在柱状图顶部标注数值
(p.get_x() + p.get_width() / 2., p.get_height()), # 设置标注位置为柱子顶部中心
# 设置标注文本的水平对齐、垂直偏移等显示参数
ha='center', va='center', xytext=(0, 5), textcoords='offset points')
plt.savefig("区域分布.png") # 保存图形
# 2.2 商品信息可视化
plt.figure(figsize=(20,5)) # 设置图形大小
# 产品名称分布柱状图
plt.subplot(1, 2, 1)
sns.countplot(x='product_name', data=data, palette='Set2') # 绘制产品名称分布柱状图
plt.xlabel('产品名称', fontsize=16) # 设置x轴标签
plt.ylabel('数量', fontsize=16) # 设置y轴标签
plt.title('产品分布', fontsize=20) # 设置标题
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
plt.gca().annotate(format(p.get_height(), '.0f'), # 获取当前坐标轴并在柱状图顶部标注数值
(p.get_x() + p.get_width() / 2., p.get_height()), # 设置标注位置为柱子顶部中心
# 设置标注文本的水平对齐、垂直偏移等显示参数
ha='center', va='center', xytext=(0, 5), textcoords='offset points')
# 产品类型分布柱状图
plt.subplot(1, 2, 2)
sns.countplot(x='category', data=data, palette='Set2') # 绘制产品类型分布柱状图
plt.xlabel('产品类型', fontsize=16) # 设置x轴标签
plt.ylabel('数量', fontsize=16) # 设置y轴标签
plt.title('产品类型分布', fontsize=20) # 设置标题
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
plt.gca().annotate(format(p.get_height(), '.0f'), # 获取当前坐标轴并在柱状图顶部标注数值
(p.get_x() + p.get_width() / 2., p.get_height()), # 设置标注位置为柱子顶部中心
# 设置标注文本的水平对齐、垂直偏移等显示参数
ha='center', va='center', xytext=(0, 5), textcoords='offset points')
plt.savefig("商品信息.png") # 保存图形
#总体而言,茶叶是销量最多的产品类型,远超其他产品类型。
#具体产品分布上,销量最多的产品是古田银耳,共1474单;其次是安溪铁观音,共1461单;第三名是宁德大黄鱼,共1438单。
# 销售相关指标分布可视化
plt.figure(figsize=(20,6)) # 设置图形大小
# 价格分布直方图
plt.subplot(1, 3, 1)
sns.distplot(data['price'], kde=True, color='blue') # 绘制价格分布直方图
plt.title('价格分布', fontsize=16) # 设置标题
plt.xlabel('价格', fontsize=12) # 设置x轴标签
plt.ylabel('频率', fontsize=12) # 设置y轴标签
# 数量分布直方图
plt.subplot(1, 3, 2)
sns.distplot(data['quantity'], kde=True, color='green') # 绘制数量分布直方图
plt.title('数量分布', fontsize=16) # 设置标题
plt.xlabel('数量', fontsize=12) # 设置x轴标签
plt.ylabel('频率', fontsize=12) # 设置y轴标签
# 销售额分布直方图
plt.subplot(1, 3, 3)
sns.distplot(data['sales_amount'], kde=True, color='orange') # 绘制销售额分布直方图
plt.title('销售额分布', fontsize=16) # 设置标题
plt.xlabel('销售额', fontsize=12) # 设置x轴标签
plt.ylabel('频率', fontsize=12) # 设置y轴标签
plt.savefig("销售.png") # 保存图形
# 2.3 平台及优惠信息可视化
plt.figure(figsize=(20,12)) # 设置图形大小
# 平台分布柱状图
plt.subplot(2, 3, 1)
sns.countplot(x='channel', data=data, palette='Set2') # 绘制平台分布柱状图
plt.title('平台分布', fontsize=16) # 设置标题
plt.xlabel('平台', fontsize=14) # 设置x轴标签
plt.ylabel('频数', fontsize=14) # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
plt.gca().annotate(format(p.get_height(), '.0f'), # 获取当前坐标轴并在柱状图顶部标注数值
(p.get_x() + p.get_width() / 2., p.get_height()), # 设置标注位置为柱子顶部中心
# 设置标注文本的水平对齐、垂直偏移等显示参数
ha='center', va='center', xytext=(0, 5), textcoords='offset points')
# 平台比例饼图
plt.subplot(2, 3 ,2)
plt.pie(data['channel'].value_counts(), # 绘制饼图
labels=data['channel'].value_counts().index, # 统计各个值的出现频次
autopct='%1.1f%%') # 绘制平台比例饼图
plt.title('平台比例', fontsize=16) # 设置标题
# 优惠情况分布柱状图
plt.subplot(2,3, 3)
sns.countplot(x='promotion', data=data, palette='Set2') # 绘制优惠情况分布柱状图
plt.title('优惠情况分布', fontsize=16) # 设置标题
plt.xlabel('优惠情况', fontsize=14) # 设置x轴标签
plt.ylabel('频数', fontsize=14) # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
plt.gca().annotate(format(p.get_height(), '.0f'), # 获取当前坐标轴并在柱状图顶部标注数值
(p.get_x() + p.get_width() / 2., p.get_height()), # 设置标注位置为柱子顶部中心
# 设置标注文本的水平对齐、垂直偏移等显示参数
ha='center', va='center', xytext=(0, 5), textcoords='offset points')
# 优惠情况比例饼图
plt.subplot(2, 3, 4)
plt.pie(data['promotion'].value_counts(), # 绘制饼图
labels=data['promotion'].value_counts().index, # 统计各个值的出现频次
autopct='%1.1f%%') # 绘制优惠情况比例饼图
plt.title('优惠情况比例', fontsize=16) # 设置标题
# 是否返单分布柱状图
plt.subplot(2, 3, 5)
sns.countplot(x='return_flag', data=data, palette='Set2') # 绘制是否返单分布柱状图
plt.title('是否返单分布', fontsize=16) # 设置标题
plt.xlabel('是否返单', fontsize=14) # 设置x轴标签
plt.ylabel('频数', fontsize=14) # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
plt.gca().annotate(format(p.get_height(), '.0f'), # 获取当前坐标轴并在柱状图顶部标注数值
(p.get_x() + p.get_width() / 2., p.get_height()), # 设置标注位置为柱子顶部中心
# 设置标注文本的水平对齐、垂直偏移等显示参数
ha='center', va='center', xytext=(0, 5), textcoords='offset points')
# 是否返单比例饼图
plt.subplot(2, 3, 6)
plt.pie(data['return_flag'].value_counts(), # 绘制饼图
labels=data['return_flag'].value_counts().index, # 统计各个值的出现频次
autopct='%1.1f%%') # 绘制是否返单比例饼图
plt.title('是否返单比例', fontsize=16) # 设置标题
plt.savefig("平台及优惠信息.png") # 保存图形
#从平台的分布上可以了解到,淘宝订单数量是最多的,占比29.6%;京东平台订单数量第二,共2504单,占比25%;第三是拼多多平台;自由平台的订单数量最少。
#从优惠情况可以了解到,没有优惠的订单占比50.3%,有优惠的订单占比49.7%。其中,满减订单占比29.4%,折扣订单占比20.3%。
#是否返单的情况可以了解到,有返单的订单占比4.8%,没有返单的订单占比95.2%。sns.distplot 为旧版接口(seaborn 0.11 起弃用、≥0.14 已移除),新版请改用 sns.histplot;另有 sns.countplot(palette=...) 未指定 hue 的写法在 seaborn ≥0.13 会给出弃用警告。平台代码保持原样,以平台判定为准。
预期输出:数据文件平台内置,本地无数据,具体数值以平台运行结果为准。判读要点:
- 文本输出:
head、info、各列缺失值计数、重复值计数——先确认数据可用性,再进入读图。 - 图形输出(特征箱型图.png):五个数值特征的箱型图。判读要点:中位数位置、箱体宽度(离散程度)、离群点数量与方向。
- 图形输出(顾客信息.png、区域分布.png、商品信息.png、销售.png、平台及优惠信息.png):按视角逐一判读——顾客结构(哪个年龄组是主体、性别比例)、区域集中度、品类与单品格局(头部品类与头部单品是否一致)、价格/数量/销售额的分布形态(是否右偏)、渠道与优惠结构、返单占比的量级。
- 平台原始代码的块内注释保留了作者的读图结论(含具体占比数字),可作对照,但最终以平台运行结果为准。
# 注:processed_data.csv数据文件本地没有,但平台已经内置
# =============================================================================
# 题目: 农产品交易数据预处理
# =============================================================================
# 本代码块演示农产品电商交易数据的加载和初步检查。数据预处理是分析
# 的第一步,通过查看数据结构、类型和基本统计,我们可以快速了解数据
# 的质量,为后续分析奠定基础。
# ==================== 导入必要的库 ====================
import pandas as pd # 导入pandas库,用于数据操作和分析
import numpy as np # 导入numpy库,用于数值计算
import matplotlib.pyplot as plt # 导入matplotlib库,用于数据可视化
import seaborn as sns # 导入seaborn库,用于绘制高级统计图形
import warnings # 导入warnings库,用于处理警告信息
warnings.filterwarnings('ignore') # 忽略警告信息,保持输出整洁
# ==================== 设置中文显示 ====================
plt.rcParams['font.sans-serif'] = ['Source Han Serif SC', 'SimHei'] # 规范字体在前,SimHei 仅作后备 # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# ==================== 读取数据 ====================
data = pd.read_csv('processed_data.csv') # 读取CSV文件到DataFrame
# ==================== 输出数据基本信息 ====================
print('数据形状:', data.shape) # 打印数据维度(行数,列数)
print('\n数据前5行:') # 打印标题
print(data.head()) # 打印前5行数据,快速浏览数据结构
print('\n数据信息:') # 打印标题
print(data.info()) # 打印数据信息(列名、数据类型、非空值数量)
print('\n描述统计:') # 打印标题
print(data.describe().T) # 打印描述统计(转置显示更易读)代码深度解析:
- 数据结构:
shape: 返回(行数, 列数)info(): 显示每列的数据类型和非空值数量describe().T: 转置描述统计表,更易阅读
- 数据类型检查:
- 数值型(int/float): 年龄、金额、数量
- 字符型(object): 产品名称、类别
- 日期型(datetime): 需要转换
- 中文配置:
SimHei(黑体): 常用中文字体unicode_minus=False: 修复负号显示问题
28.3.2 产品分类映射
补充说明:产品分类学
产品分类是电商数据分析的基础。常见的分类方法:
- 功能分类: 按产品用途分类
- 属性分类: 按产品属性分类
- 场景分类: 按使用场景分类
在农产品电商中,我们通常采用混合分类策略:
- 一级分类: 茶叶、水果、水产品等大类
- 二级分类: 具体品种(铁观音、武夷岩茶)
- 三级分类: 产地、等级等细分
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# =============================================================================
# 题目: 农产品分类映射
# =============================================================================
# 本代码块演示如何将具体产品名称映射到标准分类体系。分类映射是数据清洗
# 的重要步骤,统一的产品分类便于后续的聚合分析和可视化。
# ==================== 产品名称到分类的映射 ====================
category_mapping = {
'安溪铁观音': '茶叶', # 安溪铁观音属于茶叶类
'武夷岩茶': '茶叶', # 武夷岩茶属于茶叶类
'福州茉莉花': '茶叶', # 福州茉莉花属于茶叶类
'古田银耳': '食用菌', # 古田银耳属于食用菌类
'建宁莲子': '中药材', # 建宁莲子属于中药材类
'琯溪蜜柚': '水果', # 琯溪蜜柚属于水果类
'宁德大黄鱼': '水产品' # 宁德大黄鱼属于水产品类
}
# ==================== 应用映射 ====================
# map()函数根据字典映射,将产品名称转换为分类
# fillna()保留未匹配的值,保持原分类不变
data['category'] = data['product_name'].map(category_mapping).fillna(data['category'])
# ==================== 验证映射结果 ====================
print('分类映射结果:') # 打印标题
print(data[['product_name', 'category']].drop_duplicates().head(10)) # 去重查看唯一映射
# ==================== 验证分类完整性 ====================
print('\n分类统计:') # 打印标题
print(data['category'].value_counts()) # 统计各分类的产品数量代码深度解析:
map()函数:- 根据字典进行键值映射
fillna(): 处理未匹配的值(保留原分类)- 比循环映射高效100倍以上
- 数据质量验证:
drop_duplicates(): 去重查看唯一映射value_counts(): 统计各分类产品数量
28.4 数据质量检查
28.4.1 缺失值分析
理论背景:缺失值的类型
根据Rubin(1976)的分类,缺失值分为三类:
- MCAR (Missing Completely At Random):
- 缺失与任何变量无关
- 完全随机发生
- 处理方法:直接删除或插补
- MAR (Missing At Random):
- 缺失与观测变量相关
- 例如:高价值客户更不愿填写收入
- 处理方法:多重插补
- MNAR (Missing Not At Random):
- 缺失与未观测变量相关
- 例如:极高收入者拒绝回答
- 处理方法:建模机制
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# =============================================================================
# 题目: 数据质量检查与可视化
# =============================================================================
# 本代码块演示如何进行数据质量检查,包括缺失值分析、重复值检测等。
# 数据质量直接影响分析结果的可靠性,因此是数据预处理的关键环节。
# ==================== 缺失值检查 ====================
print('缺失值统计:') # 打印标题
missing_counts = data.isnull().sum() # 统计每列的缺失值数量
missing_pct = (missing_counts / len(data)) * 100 # 计算缺失值比例
missing_df = pd.DataFrame({ # 将结果整理为DataFrame
'缺失数': missing_counts, # 缺失值数量
'缺失比例': missing_pct # 缺失值百分比
})
print(missing_df) # 打印缺失值统计表
# ==================== 缺失值可视化 ====================
plt.figure(figsize=(10, 6)) # 创建图形,尺寸10×6英寸
missing_df['缺失比例'].plot(kind='bar', color='steelblue', alpha=0.7) # 绘制柱状图
plt.title('各字段缺失值比例', fontsize=14) # 设置标题
plt.xlabel('字段名', fontsize=12) # 设置x轴标签
plt.ylabel('缺失比例(%)', fontsize=12) # 设置y轴标签
plt.xticks(rotation=45) # x轴标签旋转45度
plt.grid(axis='y', alpha=0.3) # 显示y轴网格
plt.axhline(y=5, color='red', linestyle='--', label='5%阈值') # 添加5%阈值参考线
plt.legend() # 显示图例
plt.tight_layout() # 调整布局
plt.show() # 显示图形
# ==================== 重复值检查 ====================
print(f'\n重复值数量: {data.duplicated().sum()}') # 打印重复行数量
print(f'重复值比例: {data.duplicated().sum() / len(data) * 100:.2f}%') # 打印重复值比例
# ==================== 数据清洗 ====================
data_clean = data.drop_duplicates() # 删除重复行
print(f'\n清洗后数据形状: {data_clean.shape}') # 打印清洗后的数据维度
print(f'数据保留率: {len(data_clean) / len(data) * 100:.2f}%') # 计算数据保留率28.4.2 异常值检测
理论背景:异常值的识别方法
异常值(Outlier)指显著偏离其他观测值的数据点。常见识别方法:
- 基于统计量:
- 3σ原则: 超出均值±3倍标准差
- IQR方法: 超出Q1-1.5×IQR或Q3+1.5×IQR
- 基于可视化:
- 箱线图: 直观显示离群点
- 散点图: 识别偏离整体趋势的点
- 基于模型:
- DBSCAN聚类: 密度较低的点
- 孤立森林: 异常得分高的点
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# =============================================================================
# 题目: 农产品价格异常值检测
# =============================================================================
# 本代码块演示如何使用箱线图和IQR方法检测异常值。异常值可能由数据录入错误、
# 测量误差或真实的极端情况造成,需要根据业务判断如何处理。
# ==================== 选择数值型字段 ====================
numeric_cols = data_clean.select_dtypes(include=[np.number]).columns # 选择数值类型的列
# ==================== 绘制箱线图 ====================
fig, axes = plt.subplots(2, 2, figsize=(14, 10)) # 创建2×2子图布局
axes = axes.flatten() # 将子图数组展平为一维
# 遍历前4个数值字段,绘制箱线图
for i, col in enumerate(numeric_cols[:4]): # 只取前4个字段
data_clean.boxplot(column=col, ax=axes[i]) # 绘制箱线图
axes[i].set_title(f'{col} - 箱线图', fontsize=12) # 设置子图标题
axes[i].grid(axis='y', alpha=0.3) # 显示y轴网格
plt.tight_layout() # 调整布局
plt.show() # 显示图形
# ==================== 统计异常值数量(IQR方法) ====================
def detect_outliers_iqr(series):
"""
使用IQR方法检测异常值
参数:
series: pandas Series,待检测的数据序列
返回:
异常值数量
"""
Q1 = series.quantile(0.25) # 计算第一四分位数(25%分位)
Q3 = series.quantile(0.75) # 计算第三四分位数(75%分位)
IQR = Q3 - Q1 # 计算四分位距
lower = Q1 - 1.5 * IQR # 计算下界(小于此值为异常值)
upper = Q3 + 1.5 * IQR # 计算上界(大于此值为异常值)
return ((series < lower) | (series > upper)).sum() # 返回异常值数量
# ==================== 输出异常值统计 ====================
print('\n异常值统计(IQR方法):') # 打印标题
for col in numeric_cols: # 遍历所有数值字段
outlier_count = detect_outliers_iqr(data_clean[col].dropna()) # 检测异常值数量
print(f'{col}: {outlier_count}个异常值 ({outlier_count/len(data_clean)*100:.2f}%)') # 打印结果28.5 数据分布分析
28.5.1 单变量分布
理论背景:分布的特征
描述数据分布的三个维度:
- 集中趋势:
- 均值: 所有值的平均
- 中位数: 排序后的中间值
- 众数: 出现频率最高的值
- 离散程度:
- 方差: 数据偏离均值的程度
- 标准差: 方差的平方根
- 极差: 最大值与最小值的差
- 分布形状:
- 偏度: 分布的对称性
- 峰度: 分布的尖锐程度
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# =============================================================================
# 题目: 农产品销售额分布分析
# =============================================================================
# 本代码块演示如何分析销售额的分布特征。通过直方图和箱线图,我们可以
# 直观地观察数据的分布形态、中心趋势和离散程度,为业务决策提供依据。
# ==================== 提取销售额数据 ====================
sales = data_clean['sales_amount'] # 提取销售额列
# ==================== 绘制分布图 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6)) # 创建1×2子图布局
# 子图1:直方图
axes[0].hist(sales, bins=50, color='steelblue', alpha=0.7, edgecolor='black') # 绘制直方图
# 添加均值参考线
axes[0].axvline(sales.mean(), color='red', linestyle='--', linewidth=2, label=f'均值={sales.mean():.2f}')
# 添加中位数参考线
axes[0].axvline(sales.median(), color='green', linestyle='--', linewidth=2, label=f'中位数={sales.median():.2f}')
axes[0].set_title('销售额分布直方图', fontsize=14) # 设置标题
axes[0].set_xlabel('销售额(元)', fontsize=12) # 设置x轴标签
axes[0].set_ylabel('频数', fontsize=12) # 设置y轴标签
axes[0].legend(fontsize=10) # 显示图例
axes[0].grid(axis='y', alpha=0.3) # 显示y轴网格
# 子图2:箱线图
axes[1].boxplot(sales, vert=True) # 绘制箱线图,垂直方向
axes[1].set_title('销售额箱线图', fontsize=14) # 设置标题
axes[1].set_ylabel('销售额(元)', fontsize=12) # 设置y轴标签
axes[1].grid(axis='y', alpha=0.3) # 显示y轴网格
plt.tight_layout() # 调整布局
plt.show() # 显示图形
# ==================== 计算分布统计量 ====================
from scipy.stats import skew, kurtosis # 导入偏度和峰度函数
print('\n销售额分布统计:') # 打印标题
print(f'均值: {sales.mean():.2f}') # 打印均值
print(f'中位数: {sales.median():.2f}') # 打印中位数
print(f'标准差: {sales.std():.2f}') # 打印标准差
print(f'偏度: {skew(sales):.4f}') # 打印偏度(正值右偏,负值左偏)
print(f'峰度: {kurtosis(sales):.4f}') # 打印峰度(正值尖峰,负值平峰)
# ==================== 偏度解读 ====================
if abs(skew(sales)) < 0.5: # 偏度绝对值小于0.5
skew_interpret = '接近对称分布' # 分布接近对称
elif skew(sales) > 0: # 偏度大于0
skew_interpret = '右偏分布(存在高值异常)' # 正偏(右偏),存在高值异常
else: # 偏度小于0
skew_interpret = '左偏分布(存在低值异常)' # 负偏(左偏),存在低值异常
print(f'\n偏度解读: {skew_interpret}') # 打印偏度解读28.5.2 类别分布
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# =============================================================================
# 题目: 农产品品类分布分析
# =============================================================================
# 本代码块演示如何分析不同品类农产品的销售情况。通过聚合统计和可视化,
# 我们可以识别核心品类、辅助品类和长尾品类,为商品策略和库存管理提供依据。
# ==================== 按品类聚合统计 ====================
category_sales = data_clean.groupby('category')['sales_amount'].agg([ # 按分类分组,聚合销售额
'sum', # 计算总销售额
'count', # 计算订单数
'mean' # 计算平均单价
])
category_sales.columns = ['总销售额', '订单数', '平均单价'] # 重命名列
category_sales = category_sales.sort_values('总销售额', ascending=False) # 按总销售额降序排序
# ==================== 输出统计结果 ====================
print('\n品类销售统计:') # 打印标题
print(category_sales) # 打印品类统计表
# ==================== 可视化 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6)) # 创建1×2子图布局
# 子图1:总销售额柱状图
category_sales['总销售额'].plot(kind='bar', ax=axes[0], color='steelblue', alpha=0.7) # 绘制柱状图
axes[0].set_title('各品类总销售额', fontsize=14) # 设置标题
axes[0].set_xlabel('产品类别', fontsize=12) # 设置x轴标签
axes[0].set_ylabel('销售额(元)', fontsize=12) # 设置y轴标签
axes[0].tick_params(axis='x', rotation=45) # x轴标签旋转45度
axes[0].grid(axis='y', alpha=0.3) # 显示y轴网格
# 子图2:订单数饼图
axes[1].pie(category_sales['订单数'], labels=category_sales.index, autopct='%1.1f%%',
startangle=90, colors=sns.color_palette('Set3')) # 绘制饼图
axes[1].set_title('各品类订单数占比', fontsize=14) # 设置标题
plt.tight_layout() # 调整布局
plt.show() # 显示图形28.6 时间序列初步分析
农产品数据的特殊性:
进入时间维度之前,先回顾农产品数据区别于一般零售数据的四个特点,它们决定了本章关注的周期与波动:
- 季节性: 农产品生产受自然周期影响
- 地域性: 不同地区消费习惯差异大
- 易腐性: 保鲜期短,库存管理至关重要
- 价格波动: 受供需关系和政策影响明显
28.6.1 时间特征提取
补充说明:时间序列的特征
时间序列数据具有独特特征:
- 趋势性(Trend): 长期上升或下降趋势
- 季节性(Seasonality): 固定周期的波动
- 周期性(Cyclicity): 不固定周期的波动
- 随机性(Random): 不可预测的噪声
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# =============================================================================
# 题目: 时间特征提取与分析
# =============================================================================
# 本代码块演示如何从日期时间字段中提取时间特征(年、月、日、星期、小时等)。
# 时间特征提取是时间序列分析的基础,有助于发现周期性模式和季节性规律。
# ==================== 确保日期格式正确 ====================
data_clean['下单日期'] = pd.to_datetime(data_clean['order_date']) # 转换为datetime类型
# ==================== 提取时间特征 ====================
data_clean['年'] = data_clean['下单日期'].dt.year # 提取年份
data_clean['月'] = data_clean['下单日期'].dt.month # 提取月份(1-12)
data_clean['日'] = data_clean['下单日期'].dt.day # 提取日(1-31)
data_clean['星期'] = data_clean['下单日期'].dt.dayofweek # 提取星期(0=周一,6=周日)
data_clean['小时'] = data_clean['下单日期'].dt.hour # 提取小时(0-23)
# ==================== 按月统计 ====================
monthly_sales = data_clean.groupby('月')['sales_amount'].agg([ # 按月份分组统计
'sum', # 总销售额
'count' # 订单数
])
monthly_sales.columns = ['月销售额', '月订单数'] # 重命名列
# ==================== 输出统计结果 ====================
print('月度销售统计:') # 打印标题
print(monthly_sales) # 打印月度统计表
# ==================== 可视化月度趋势 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6)) # 创建1×2子图布局
monthly_sales['月销售额'].plot(ax=axes[0], marker='o', linewidth=2) # 绘制折线图
axes[0].set_title('月销售额趋势', fontsize=14) # 设置标题
axes[0].set_xlabel('月份', fontsize=12) # 设置x轴标签
axes[0].set_ylabel('销售额(元)', fontsize=12) # 设置y轴标签
axes[0].grid(True, alpha=0.3) # 显示网格
monthly_sales['月订单数'].plot(kind='bar', ax=axes[1], color='coral', alpha=0.7) # 绘制柱状图
axes[1].set_title('月订单数分布', fontsize=14) # 设置标题
axes[1].set_xlabel('月份', fontsize=12) # 设置x轴标签
axes[1].set_ylabel('订单数', fontsize=12) # 设置y轴标签
axes[1].grid(axis='y', alpha=0.3) # 显示y轴网格
plt.tight_layout() # 调整布局
plt.show() # 显示图形28.6.2 周末效应分析
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# =============================================================================
# 题目: 周末效应分析
# =============================================================================
# 本代码块演示如何分析工作日与周末的消费差异。周末效应分析可以帮助
# 企业制定营销策略和库存管理计划,例如周末增加营销投入或调整物流配送。
# ==================== 标记周末 ====================
# isin([5, 6])判断星期是否为5(周六)或6(周日)
data_clean['是否周末'] = data_clean['星期'].isin([5, 6]) # 创建周末标记列
# ==================== 周末vs工作日对比 ====================
weekend_comparison = data_clean.groupby('是否周末')['sales_amount'].agg([ # 按是否周末分组
'mean', # 平均销售额
'sum', # 总销售额
'count' # 订单数
])
weekend_comparison.index = ['工作日', '周末'] # 重命名索引
# ==================== 输出对比结果 ====================
print('周末效应分析:') # 打印标题
print(weekend_comparison) # 打印对比表
# ==================== 可视化对比 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6)) # 创建1×2子图布局
# 子图1:平均销售额对比
weekend_comparison['mean'].plot(kind='bar', ax=axes[0], color=['skyblue', 'orange'], alpha=0.7) # 绘制柱状图
axes[0].set_title('平均销售额对比', fontsize=14) # 设置标题
axes[0].set_ylabel('平均销售额(元)', fontsize=12) # 设置y轴标签
axes[0].tick_params(axis='x', rotation=0) # x轴标签不旋转
axes[0].grid(axis='y', alpha=0.3) # 显示y轴网格
# 子图2:总销售额对比
weekend_comparison['sum'].plot(kind='bar', ax=axes[1], color=['skyblue', 'orange'], alpha=0.7) # 绘制柱状图
axes[1].set_title('总销售额对比', fontsize=14) # 设置标题
axes[1].set_ylabel('总销售额(元)', fontsize=12) # 设置y轴标签
axes[1].tick_params(axis='x', rotation=0) # x轴标签不旋转
axes[1].grid(axis='y', alpha=0.3) # 显示y轴网格
plt.tight_layout() # 调整布局
plt.show() # 显示图形28.7 多变量关系探索
28.7.1 价格与销量的关系
理论背景:价格弹性
价格弹性(Price Elasticity)衡量需求量对价格变化的敏感度:
\[ E_p = \frac{\%\Delta Q}{\%\Delta P} = \frac{\partial Q}{\partial P} \times \frac{P}{Q} \]
- |Ep| > 1: 富有弹性(奢侈品)
- |Ep| < 1: 缺乏弹性(必需品)
- |Ep| = 1: 单位弹性
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# =============================================================================
# 题目: 价格与销量关系分析
# =============================================================================
# 本代码块演示如何分析产品价格与销量之间的关系。价格-销量关系是
# 定价策略的核心,通过相关性分析和可视化,可以评估产品的价格弹性。
# ==================== 按产品统计价格与销量 ====================
product_stats = data_clean.groupby('product_name').agg({ # 按产品名称分组
'sales_amount': 'sum', # 总销售额
'quantity': 'sum', # 总销量
'unit_price': 'mean' # 平均单价
})
product_stats['单价'] = product_stats['sales_amount'] / product_stats['quantity'] # 计算实际单价
# ==================== 计算相关系数 ====================
corr_coef = product_stats[['单价', 'quantity']].corr().iloc[0, 1] # 计算价格与销量的相关系数
print(f'价格与销量的相关系数: {corr_coef:.4f}') # 打印相关系数
# ==================== 可视化 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6)) # 创建1×2子图布局
# 子图1:散点图
axes[0].scatter(product_stats['单价'], product_stats['quantity'], s=100, alpha=0.6) # 绘制散点图
axes[0].set_title(f'价格-销量散点图 (相关系数={corr_coef:.4f})', fontsize=14) # 设置标题
axes[0].set_xlabel('单价(元)', fontsize=12) # 设置x轴标签
axes[0].set_ylabel('销量', fontsize=12) # 设置y轴标签
axes[0].grid(True, alpha=0.3) # 显示网格
# 添加产品标签
for idx, row in product_stats.iterrows(): # 遍历每个产品
axes[0].annotate(idx, (row['单价'], row['quantity']), fontsize=8) # 添加产品名称标签
# 子图2:热力图
sns.heatmap(product_stats[['单价', 'quantity']].corr(), annot=True, cmap='coolwarm',
center=0, ax=axes[1], cbar_kws={'label': '相关系数'}) # 绘制相关性热力图
axes[1].set_title('相关性热力图', fontsize=14) # 设置标题
plt.tight_layout() # 调整布局
plt.show() # 显示图形28.7.2 产品关联分析
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# =============================================================================
# 题目: 产品共现分析
# =============================================================================
# 本代码块演示如何分析产品之间的关联关系。产品共现分析可以发现哪些
# 产品经常被一起购买,为捆绑销售、推荐系统和货架摆放提供依据。
# ==================== 导入组合函数 ====================
from itertools import combinations # 导入combinations函数,用于生成组合
# ==================== 获取每个订单的产品列表 ====================
order_products = data_clean.groupby('order_id')['product_name'].apply(list) # 按订单分组,获取产品列表
# ==================== 统计产品对 ====================
co_occurrence = {} # 创建空字典,存储产品对的共现次数
for products in order_products: # 遍历每个订单的产品列表
for combo in combinations(sorted(products), 2): # 生成所有2个产品的组合
co_occurrence[combo] = co_occurrence.get(combo, 0) + 1 # 统计共现次数
# ==================== 转换为DataFrame ====================
co_df = pd.DataFrame(list(co_occurrence.items()), columns=['产品对', '共现次数']) # 转换为DataFrame
co_df = co_df.sort_values('共现次数', ascending=False).head(10) # 按共现次数降序排序,取前10
# ==================== 输出结果 ====================
print('Top 10产品组合:') # 打印标题
print(co_df) # 打印共现次数最高的10个产品对
# ==================== 可视化 ====================
plt.figure(figsize=(12, 6)) # 创建图形
plt.barh(range(len(co_df)), co_df['共现次数'], color='steelblue', alpha=0.7) # 绘制水平柱状图
plt.yticks(range(len(co_df)), [f'{p1} + {p2}' for p1, p2 in co_df['产品对']]) # 设置y轴标签
plt.xlabel('共现次数', fontsize=12) # 设置x轴标签
plt.title('产品共现分析(Top 10)', fontsize=14) # 设置标题
plt.grid(axis='x', alpha=0.3) # 显示x轴网格
plt.tight_layout() # 调整布局
plt.show() # 显示图形28.8 本章小结
本章新增的技能要点:
- EDA先体检后读图:
info、isnull().sum()、duplicated().sum()三件套是一切图形结论的可信度前提。 - 图随数选:数值变量看分布用直方图/箱线图,类别变量看结构用计数柱状图/饼图,占比结论优先用饼图的百分比标注。
- 映射兜底写法:
data['product_name'].map(字典).fillna(data['category'])批量改写类别列且不丢未匹配值。 - 子图网格排版:
plt.subplot(行,列,序号)把多视角图形组织到一张画布上,读图时按行列分块归纳。 - 柱顶数值标注:
plt.gca().patches遍历加注,是让计数图可直接读数的通用手法。
易错点:
map(category_mapping)不接fillna(data['category']):字典外的新产品类别变缺失值,计数图把这些行悄悄丢掉,图上只表现为”柱子矮一点”,须对照计数表核对总数- 本地用
sns.distplot复现会失败:该接口 seaborn ≥0.14 已移除,本地应改用sns.histplot;平台代码保持原样输入,以平台判定为准 - 把右偏销售额分布的均值当”典型订单金额”读:少数大额订单把均值拉高,报”平均”应同时给出中位数或直方图形态
- 一见箱线图离群点就删:超出 1.5×IQR 的点可能是大宗订单或真实高价值客户,先按业务区分录入错误与真实极端值再决定去留
- 返单占比偏低就立刻发复购券:
return_flag的定义、分母与观察期(近期订单尚未到期,占比天然偏低)未核实前,一个全站数字定位不了原因,应分渠道、分品类下钻 - 年龄分组换了口径还沿用旧结论:固定五档读年龄结构、十分位读边界密度,两图对照前先对齐分组口径
28.9 动手与思考
以下练习每题附参考答案(默认折叠);概念题与变式题可对照自查,商业判断题不设唯一答案,判断依据与口径比结论更重要。
概念辨析:直方图与箱线图都能刻画数值分布,各自的优势场景是什么?判读离群点时两者如何互补?
参考答案(点开前请先独立完成)
解题思路:直方图的优势在“形状”——它把数值切成连续区间,能看出分布集中在哪里、是否右偏、有没有双峰、尾部拖多长(如销售额直方图常见的右偏形态:多数订单小额、少数大额拉出长尾)。箱线图的优势在“摘要对比”——只用中位数、四分位数、须与离群点几个量就把分布的位置与离散度画出来,占位小,一张画布可以并排放多个变量(平台任务把价格、数量、年龄、返单、销售额五个箱线图排在同一画布,用的正是这一点)。离群点判读上两者互补:箱线图有明确的判定规则(超出四分位距1.5倍范围的点逐一标出),能直接数出离群点的个数与方向,但看不出这些点在尾部如何堆积;直方图能看到尾部形态——离群值是连成一段还是孤立几个、长尾在哪一侧,但“离群”没有明确判定线。稳妥的流程是先用箱线图判定有没有离群、有多少,再用直方图看离群段的形状并寻找成因线索(录入错误、大宗订单还是真实高价值客户)。
回扣本章:对应“本章新增的技能要点”第2条(图随数选:看形状用直方图,摘要对比用箱线图)。
概念辨析:
data['category'] = data['product_name'].map(category_mapping).fillna(data['category'])中fillna起什么作用?若只做map而不兜底,数据会发生什么?参考答案(点开前请先独立完成)
解题思路:
map(字典)对字典里不存在的键返回缺失值;fillna(data['category'])传入的是原来的类别列,按索引一一对位,把没有匹配上的行回填为它们原本的类别值。合起来的作用是“批量改写已知产品、原样保留未知产品”。若只做map不兜底:字典里没有的新产品,其类别列直接变成缺失值,随后的value_counts与计数图会把这些行悄悄丢掉——品类计数虚低、各行计数加总对不上订单总数。本地实测(4行中1行的产品名不在字典里):只map后缺失值有1个,计数表只能统计到3行对4行;fillna回填后缺失归零、计数恢复完整。这种错误在图上几乎看不出来(只是柱子矮了一点),这正是“读图前先查计数表与缺失值”的原因。回扣本章:对应“本章新增的技能要点”第3条(映射兜底写法:
map(字典).fillna(原列)不丢未匹配值)与第1条(EDA先体检后读图)。变式任务:把年龄分组边界从”18-25/26-35/36-45/46-55/56+“改为等距十分位分组,读图结论可能如何变化?
参考答案(点开前请先独立完成)
改造思路:固定边界分组用
pd.cut或平台数据自带的age_group列,边界是人为设定的整数年龄;等距十分位改用pd.qcut(data['customer_age'], 10),边界由数据的分位数决定、每档人数设计为大致相等。代码上改两处:生成十分位新列;countplot的x与order换成该列(区间按左端排序,保证x轴从低龄到高龄)。注意qcut不能把取值相同的年龄拆开,各档人数只会接近相等而非严格相等。# 变式代码:年龄分组由固定五档改为等距十分位(分组与绘图各改一处) data['年龄十分位'] = pd.qcut(data['customer_age'], 10) # ← 改动点1:固定边界改为按人数等量切分的十分位 decile_order = data['年龄十分位'].value_counts().sort_index().index # ← 新增:区间按左端排序,x轴从低龄到高龄 plt.figure(figsize=(20, 10)) # 与平台任务一致 sns.countplot(data=data, x='年龄十分位', color='orange', order=decile_order) # ← 改动点2:x轴与order换成十分位列 plt.title('顾客年龄十分位分布') # ← 标题同步更新 plt.xticks(rotation=45, ha='right') # ← 新增:区间标签较长,旋转防重叠结构性判读:柱数由5根变为10根,各柱高度大体拉平(按订单数等量切分),图的读法随之改变:固定边界图回答“哪个人口段订单最多”(柱形高低反映年龄结构);十分位图各柱接近等高,信息量转移到分位边界的年龄刻度上——边界挤得密的地方说明年龄在该处集中,稀疏的地方是长尾。判读要点:先读边界刻度、再读是否有个别分位明显偏高(说明该年龄段有超额集中),并与固定五档图对照。
本地演练版(模拟数据):在40名模拟顾客年龄上实跑两种分组:
# 本地演练版:固定五档与等距十分位两种分组对照 import pandas as pd # 导入Pandas数据分析库 import numpy as np # 导入NumPy数值计算库 rng = np.random.default_rng(7) # 固定随机种子 customer_age = pd.Series(rng.integers(18, 66, 40)) # 模拟40名顾客年龄(18—65岁) fixed_bins = pd.cut(customer_age, bins=[17, 25, 35, 45, 55, 100], labels=['18-25', '26-35', '36-45', '46-55', '56+']) # 平台原分组 decile_bins = pd.qcut(customer_age, 10) # 变式:按人数等量切成十档 print('固定五档计数:') print(fixed_bins.value_counts().sort_index()) # 各档人数不均,反映年龄结构 print('十分位分组计数与区间边界:') print(decile_bins.value_counts().sort_index()) # 各档人数接近,边界由数据分位决定模拟数据演练结果(本机 peter 环境实际运行结果,仅演示流程与判读方法,真实数值以平台运行结果为准):
固定五档计数: 18-25 4 26-35 8 36-45 9 46-55 6 56+ 13 Name: count, dtype: int64 十分位分组计数与区间边界: (17.999, 27.6] 4 (27.6, 31.8] 4 (31.8, 37.5] 4 (37.5, 41.6] 4 (41.6, 45.0] 5 (45.0, 50.4] 3 (50.4, 56.0] 6 (56.0, 57.2] 2 (57.2, 61.2] 4 (61.2, 65.0] 4 Name: count, dtype: int64演练判读:固定五档下“56+”一段有13人、高居首位;十分位下各档人数在2—6之间(相同年龄不可拆分,故不完全相等),而边界刻度显示56—57.2岁这一档被挤得很窄——年龄在高段密集。同一批顾客,两种分组给出两种读法。
预期输出:数据文件由教学平台内置,本机无法复现,具体数值以平台运行结果为准。
注意:以上为本题变式的独立代码;列表 28.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应“本章新增的技能要点”第2条(图随数选:分组方式变了,同一张计数图的含义与读法也随之改变)。
变式任务:将区域分布图改为”区域×品类”的分组柱状图,改造思路是什么?预计能回答什么新问题?
参考答案(点开前请先独立完成)
改造思路:只需在
sns.countplot上加一个hue='category'参数——每个区域一组柱、组内按品类分色并排,柱顶标注循环plt.gca().patches会自动覆盖组内所有柱,无需改动;x轴仍是区域,图例自动生成。# 变式代码:区域分布由单柱改为“区域×品类”分组柱状图 plt.figure(figsize=(20, 5)) # 与平台任务一致 sns.countplot(x='region', hue='category', data=data, palette='Set2') # ← 改动点:新增hue='category',组内按品类分色并排 plt.title('区域×品类订单结构') # ← 标题同步更新 plt.xlabel('区域', fontsize=16) # 与平台任务一致 plt.ylabel('数量', fontsize=16) # 与平台任务一致 for p in plt.gca().patches: # 与平台任务一致的柱顶标注循环(自动覆盖所有柱) plt.gca().annotate(format(p.get_height(), '.0f'), (p.get_x() + p.get_width() / 2., p.get_height()), ha='center', va='center', xytext=(0, 5), textcoords='offset points') # 标注各柱订单数 plt.savefig('区域品类结构.png') # 建议另存新文件名,避免覆盖平台任务的区域分布.png结构性判读:柱数由“区域数”变为“区域数×品类数”,能回答的新问题有三:各区域的品类结构是否同质(同质则全国可用一套选品);哪些品类只在个别区域强(区域偏好或供给差异);区域总量差异中有多少来自品类构成不同(某区域总量低是因为缺品类还是每类都弱)。判读要点:组内柱高比较用相对比例,跨区域比较总量时回到各组合计;品类多时同色系难分辨,标注会拥挤,可只保留头部品类或改用百分比堆叠图。
预期输出:数据文件由教学平台内置,本机无法复现,具体数值以平台运行结果为准。
注意:以上为本题变式的独立代码;列表 28.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应“本章新增的技能要点”第4条(子图网格排版与分组柱状图同属“多视角组织到一张画布”的排版手法)。
商业判断:若平台运行结果显示返单订单占比明显偏低(具体数值以平台运行结果为准),你会优先从商品质量、售后体验、复购激励三个方向中哪个切入?如何利用本章已有的图形进一步定位原因?
参考答案(点开前请先独立完成)
参考作答框架:本题不设唯一结论,优秀作答应覆盖以下维度。
- 口径先行:先核实
return_flag的字段含义(同客再购、同单退换还是其他定义,以平台数据字典为准)、占比的分母(全部订单还是应到返单期的订单)、观察期是否给了客户足够的返单时间——近期订单尚未到期,直接算占比会天然偏低。 - 定位路径(用本章已有的图形):返单低若是全局均匀的,用渠道分布图与区域分布图核对——各渠道、各区域是否同样低,均匀则倾向激励或整体体验问题;若集中于个别品类或单品,用商品信息图(产品名称、品类计数)对照返单分布,指向商品质量;若集中于某渠道,用平台及优惠信息图核对——有优惠订单与无优惠订单的返单差异指向价格与激励,渠道间差异指向履约与售后体验。
- 判断依据:三个方向对应三类证据——质量问题的证据在品类/单品集中度与差评退货数据;售后问题的证据在渠道集中度与响应时长;激励问题的证据在优惠与返单的关联。先让数据指向方向,再投入预算,而不是先选最熟悉的工具。
- 风险考量:盲目发复购券侵蚀毛利且可能只是提前透支需求;质量误判伤害供应商关系;售后整改见效周期长。小规模实验(A/B)比全面铺开更稳妥。
- 模型边界:本章图形都是描述性的,相关不等于因果(有优惠者本就更活跃);跨期比较需要控制客户结构变化。
- 还需补充的数据:客户维度的购买间隔分布、退货与差评数据、优惠券核销率、售后响应时长、竞品价格。
- 常见错误作答形态:直接选“复购激励”发券(最常见、最贵、最未经验证);用全站一个占比数字下结论而不做分渠道、分品类的下钻。
回扣本章:对应“本章新增的技能要点”第1条(先体检后读图)与第2条(图随数选:同一批图形换个维度切,就能定位问题来源)。
- 口径先行:先核实